<script setup lang="ts">
import {
  Cartesian3,
  Cartographic,
  Primitive,
  GeometryInstance,
  PolygonGeometry,
  PolygonHierarchy,
  EllipsoidSurfaceAppearance,
  Material,
  Camera,
  ClippingPlaneCollection,
  Plane,
  Matrix4,
  Cesium3DTileset,
  Model,
  Entity,
  ModelGraphics,
  Transforms,
  JulianDate,
  Color,
} from "cesium";
import * as Cesium from "cesium";
import { ref, onMounted, watchEffect } from "vue";
import * as THREE from "three";
import TCesium from "@/utils/TCesium";

let tCesium;
const loader = new THREE.FileLoader();
const mapGroup = new THREE.Group();
THREE.Cache.enabled = true;
loader.setResponseType("json");
function animate() {
  tCesium.renderCesium();
  tCesium.renderThree();
  requestAnimationFrame(animate);
}
const positionsArr = [
  108.945, 34.205, 950, 108.975, 34.205, 950, 108.975, 34.235, 950, 108.945,
  34.235, 950,
];
const min_rc = ref(0.8); // 最小反射系数
const max_rc = ref(1.0); // 最大反射系数
const factor = ref(5); // 视线因子
const frequency = ref(2000);
const animationSpeed = ref(0.01);
const amplitude = ref(5);
const specularIntensity = ref(0.8);
const baseWaterColor = ref("rgba(45, 95, 146, 1.0)");

onMounted(async () => {
  tCesium = new TCesium("cesiumContainer", [-180, -90], [180, 90]);
  window.tCesium = tCesium;
  tCesium.cesium.viewer.scene.globe.depthTestAgainstTerrain = false;
  animate();
  const viewer = tCesium.cesium.viewer;
  reviseGlobeWater(viewer);

  // 添加镜头光晕效果
  viewer.scene.postProcessStages.add(
    Cesium.PostProcessStageLibrary.createLensFlareStage()
  );

  viewer.scene.sun.glowFactor = 10;

  const modelEntity = viewer.entities.add({
    position: Cartesian3.fromDegrees(108.959, 34.2192, 1000),
    model: {
      uri: "./models/Cesium_Air.glb",
      scale: 10.0,
    },
  });

  viewer.trackedEntity = modelEntity;
  viewer.camera.setView({
    destination: Cesium.Cartesian3.fromDegrees(108.96, 34.22, 1500),
  });
  const model = await Model.fromGltfAsync({
    url: "./models/ferrari.glb",
    scale: 10.0,
    modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(
      Cartesian3.fromDegrees(108.9617, 34.2192, 1000)
    ),
  });
  viewer.scene.primitives.add(model);

  const tileset = await Cesium.Cesium3DTileset.fromUrl("/Tileset/tileset.json");

  viewer.scene.primitives.add(tileset);
  // viewer.zoomTo(tileset, new Cesium.HeadingPitchRange(
  //   0.0, -0.5, tileset.boundingSphere.radius * 2.0
  // ))

  const positions = Cartesian3.fromDegreesArrayHeights(positionsArr);
  const waterPlane = new Primitive({
    geometryInstances: new GeometryInstance({
      geometry: new PolygonGeometry({
        polygonHierarchy: new PolygonHierarchy(positions),
        perPositionHeight: true,
      }),
    }),
    appearance: new EllipsoidSurfaceAppearance({
      material: new Material({
        fabric: {
          type: "ReflectionWaterPlane",
          uniforms: {
            image: "",
            waterNormal: new Cartesian3(),
            fadeFactor: 1.0, // 此参数不用调节
            normalMap: "水面法线.png",
            fresnelParams: new Cartesian3(0.8, 1.0, 5),
            specularMap: "czm_defaultImage",
            baseWaterColor: Color.fromCssColorString("rgba(45, 95, 146, 1.0)"),
            blendColor: new Color(),
            frequency: 2000,
            animationSpeed: 0.01,
            amplitude: 5,
            specularIntensity: 0.8,
          },
          source: `
      			uniform sampler2D image;
      			uniform vec3 waterNormal;
      			uniform vec3 fresnelParams;  // x为最小反射系数，y为最大反射系数，z为视线因子
      			uniform sampler2D specularMap;
      			uniform sampler2D normalMap;
      			uniform vec4 baseWaterColor;
      			uniform vec4 blendColor;
      			uniform float frequency;
      			uniform float animationSpeed;
      			uniform float amplitude;
      			uniform float specularIntensity;
      			uniform float fadeFactor;
      			czm_material czm_getMaterial(czm_materialInput materialInput) {
      				czm_material material = czm_getDefaultMaterial(materialInput);
      				float time = czm_frameNumber * animationSpeed;
      				// 衰减是与碎片的距离和波的频率的函数
      				float fade = max(1.0, (length(materialInput.positionToEyeEC) / 10000000000.0) * frequency 					* fadeFactor);
      				float specularMapValue = texture(specularMap, materialInput.st).r;
      				// 注意：此时不使用方向运动，只需将角度设置为0.0；
      				vec4 noise = czm_getWaterNoise(normalMap, materialInput.st * frequency, time, 0.0);
      				vec3 normalTangentSpace = noise.xyz * vec3(1.0, 1.0, (1.0 / amplitude));
      				// 当我们远离水面时，正常的扰动逐渐消失
      				normalTangentSpace.xy /= fade;
      				    // 我们接近非水区域（低镜面贴图值）时，尝试淡出正常扰动
      				normalTangentSpace = mix(vec3(0.0, 0.0, 50.0), normalTangentSpace, specularMapValue);
      				normalTangentSpace = normalize(normalTangentSpace);
      				// 获取新法向量与垂直于切平面的向量的对齐比率
      				float tsPerturbationRatio = clamp(dot(normalTangentSpace, vec3(0.0, 0.0, 1.0)), 0.0, 1.0);
      				// 随着高光贴图值的减少，淡化水的效果
      				float alpha = mix(blendColor.a, baseWaterColor.a, specularMapValue) * specularMapValue;
      				// 基础色是水和非水颜色的混合，基于镜面贴图的值，可能需要一个均匀的混合因子来更好地控制这一点
      				vec3 waterColor = mix(blendColor.rgb, baseWaterColor.rgb, specularMapValue);
      				// 漫射高光是基于法线的扰动程度
      				waterColor += (0.1 * tsPerturbationRatio);
      				// 获取水面反射的颜色
      				vec2 uv = gl_FragCoord.xy / czm_viewport.zw;  // 获取屏幕uv
      				uv.s = (1.0 - uv.s); 			// 镜像翻转
      				// 按照法线方向来扰动反射贴图的纹理坐标
      				uv += normalTangentSpace.xz / 100.0;
      				vec3 reflectColor = texture(image, uv).rgb;
      				// 加入菲涅尔现象
      				vec3 positionEC = -materialInput.positionToEyeEC;
      				vec4 positionWC = czm_inverseView * vec4(positionEC, 1.0);
      				vec3 cameraDir = normalize(positionWC.xyz - czm_viewerPositionWC);
      				vec3 n = normalize(waterNormal);
      				float fresnel = 1.0 - dot(-cameraDir, n);
      				float min_rc = fresnelParams.x; 		// 物体最小反射系数
      				float max_rc = fresnelParams.y;		 	// 物体最大反射系数
      				float f = fresnelParams.z;  				// 视线因子
      				fresnel = min_rc + (max_rc - min_rc) * pow(fresnel, f);
      				// 菲涅尔值高，代表反射强(更多显示水的反射)，值低则是折射强(也可以理解成更多显示水的本色)
      				material.diffuse = mix(waterColor, reflectColor, fresnel);
      				material.alpha = mix(alpha, 1.0, fresnel);
      				material.normal = normalize(materialInput.tangentToEyeMatrix * normalTangentSpace);
      				material.specular = specularIntensity;
      				material.shininess = 10.0;
      				return material;
      			}
      		`,
        },
      }),
      // material: Material.fromType(Material.ColorType, {
      //   color: Color.SKYBLUE,
      // }),
    }),
  });
  viewer.scene.primitives.add(waterPlane);

  const centerPosition = calcCenterPoint(positions);
  const waterNormal =
    viewer.scene.globe.ellipsoid.geodeticSurfaceNormal(centerPosition);
  // 这里准备了3个不同类型的示例数据，第一个是3dtileset，第二个采用entity加载模型，第三个则使用Model加载
  // 如果一个模型没有部分在水下，那自然就不需要裁剪了
  const models = [modelEntity, model];
  const clipPlaneCollections = models.map((model) =>
    clipAnyModel(model, centerPosition, waterNormal)
  );

  // 设置uniforms，以及监听更改
  const uniforms = waterPlane.appearance.material.uniforms;
  uniforms.waterNormal = waterNormal;
  watchEffect(() => {
    uniforms.fresnelParams = new Cartesian3(
      min_rc.value,
      max_rc.value,
      factor.value
    );
    uniforms.frequency = frequency.value;
    uniforms.animationSpeed = animationSpeed.value;
    uniforms.amplitude = amplitude.value;
    uniforms.specularIntensity = specularIntensity.value;
    uniforms.baseWaterColor = Color.fromCssColorString(baseWaterColor.value);
  });
  // 渲染。该事件的回调函数在每次渲染之前都会被执行，确保在渲染前做一些预处理操作
  viewer.scene.preRender.addEventListener(() => {
    const scene = viewer.scene as any;
    // 获取绘制区域的宽高
    // 非全屏时：宽1488，高704
    // 全屏时：宽1528，高858
    const width = scene.context.drawingBufferWidth;
    const height = scene.context.drawingBufferHeight;

    // 创建Framebuffer对象
    const fbo = new (Cesium as any).Framebuffer({
      context: scene.context,
      colorTextures: [
        new (Cesium as any).Texture({
          context: scene.context,
          width: width,
          height: height,
          pixelFormat: Cesium.PixelFormat.RGBA,
        }),
      ],
    });

    // 获取反射相机
    const reflectCamera = getMirrorCamera(
      viewer.camera,
      waterNormal,
      centerPosition
    );
    /**
     * 裁剪面是用来限制渲染区域的平面。在 Cesium 中，裁剪面通常用于控制在渲染时只显示位于某些区域的内容。
     * 裁剪面可以限制物体只在某个平面的一侧可见
     * 在这里，开启裁剪面是为了确保反射相机只渲染水面以上的场景部分（而不是渲染整个场景），以模拟水面反射效果
     */
    // 渲染前开启裁剪面
    clipPlaneCollections.forEach((clipPlaneCollection) => {
      if (clipPlaneCollection) clipPlaneCollection.enabled = true;
    });
    // 调用 renderToFbo 渲染到FBO
    renderToFbo(fbo, scene, reflectCamera);
    // 渲染后关闭裁剪面，以便恢复到正常的渲染状态，不再限制渲染区域
    clipPlaneCollections.forEach((clipPlaneCollection) => {
      if (clipPlaneCollection) clipPlaneCollection.enabled = false;
    });
    // 将渲染到 FBO 中的反射纹理应用到水面材质，最终渲染出带有反射效果的水面
    const colorTexture = fbo.getColorTexture(0);
    waterPlane.appearance.material.uniforms.image = colorTexture;
  });
});

/**计算多边形的中心点，如果传递了高度，那么中心点的高程将为指定的高度 */
function calcCenterPoint(positions: Cartesian3[], heigth?: number) {
  const total = new Cartesian3();
  positions.forEach((item) => {
    Cartesian3.add(total, item, total);
  });
  let centerPosition = Cartesian3.multiplyByScalar(
    total,
    1 / positions.length,
    total
  );
  if (heigth) {
    const centerCarto = Cartographic.fromCartesian(centerPosition);
    const newCarto = new Cartographic(
      centerCarto.longitude,
      centerCarto.latitude,
      heigth
    );
    centerPosition = Cartographic.toCartesian(newCarto);
  }
  return centerPosition;
}

/**
 * @param camera 原始相机
 * @param normal 反射面的法向量
 * @param centerPosition 反射面的中心点
 */
function getMirrorCamera(
  camera: Camera,
  normal: Cartesian3,
  centerPosition: Cartesian3
) {
  // 场景相机到反射面中心点的向量
  const cameraToCenter = Cartesian3.subtract(
    centerPosition,
    camera.positionWC,
    new Cartesian3()
  );

  const n = -Cartesian3.dot(normal, cameraToCenter);
  const t = Cartesian3.multiplyByScalar(normal, 2 * n, new Cartesian3());
  const reflectCameraPosition = Cartesian3.subtract(
    camera.positionWC,
    t,
    new Cartesian3()
  );
  // 反射向量就是 相机原本方向 - 2倍的方向在法向量的投影
  const ndir = Cartesian3.dot(normal, camera.directionWC);
  const tdir = Cartesian3.multiplyByScalar(normal, 2 * ndir, new Cartesian3());
  const reflectCameraDirection = Cartesian3.subtract(
    camera.directionWC,
    tdir,
    new Cartesian3()
  );
  Cartesian3.normalize(reflectCameraDirection, reflectCameraDirection);
  // up反射后的结果同方向计算一样
  const nup = Cartesian3.dot(normal, camera.upWC);
  const tup = Cartesian3.multiplyByScalar(normal, 2 * nup, new Cartesian3());
  const reflectCameraUp = Cartesian3.subtract(
    camera.upWC,
    tup,
    new Cartesian3()
  );

  // 创建反射相机，克隆可以继承原始相机的视锥体参数
  const reflectCamera = (Camera as any).clone(camera) as Camera;
  reflectCamera.setView({
    destination: reflectCameraPosition,
    orientation: {
      direction: reflectCameraDirection,
      up: reflectCameraUp,
    },
  });
  // reflectCamera.up = reflectCameraUp;
  // reflectCamera.direction = reflectCameraDirection;
  // reflectCamera.right = Cartesian3.cross(
  //   reflectCameraUp,
  //   reflectCameraDirection,
  //   new Cartesian3()
  // );
  return reflectCamera;
}

/**在给定的相机视角下渲染*/
/**
 * 将场景渲染到一个Framebuffer Object (FBO) 中的功能。
 * FBO是一个离屏渲染目标，允许在不直接显示到屏幕上的情况下进行图形渲染，通常用于实现后期处理、纹理生成等效果。
 */
function renderToFbo(fbo: any, scene: any, camera: Camera) {
  const frameState = scene._frameState;
  // 1. 禁用地球显示
  scene.globe.show = false;
  const context = scene.context;
  const us = context.uniformState;
  // 2. 保存原始的相机状态
  const preCamera = scene._defaultView.camera;
  // 将当前场景的相机设置为传入的相机 camera，这意味着后续的渲染将基于这个相机视角来进行
  scene._defaultView.camera = camera;
  const view = scene._defaultView;
  scene._view = view;
  // 3. 更新场景的帧状态，确保场景信息是最新的
  scene.updateFrameState();
  frameState.passes.render = true;
  frameState.passes.postProcess = scene.postProcessStages.hasSelected;
  // 4. 设置Tileset渲染的状态
  const renderTilesetPassState = new (Cesium as any).Cesium3DTilePassState({
    pass: (Cesium as any).Cesium3DTilePass.RENDER,
  });
  frameState.tilesetPassState = renderTilesetPassState;
  // 5. 设置背景颜色
  let backgroundColor = Cesium.defaultValue(
    scene.backgroundColor,
    Cesium.Color.BLACK
  );
  // if (scene._hdr) {
  // 	backgroundColor = Cesium.Color.clone(backgroundColor, new Color())
  // 	backgroundColor.red = Math.pow(backgroundColor.red, scene.gamma)
  // 	backgroundColor.green = Math.pow(backgroundColor.green, scene.gamma)
  // 	backgroundColor.blue = Math.pow(backgroundColor.blue, scene.gamma)
  // }
  frameState.backgroundColor = backgroundColor;
  frameState.atmosphere = scene.atmosphere;
  // 6. 更新场景中的雾效（fog effect）。雾效通常在渲染时影响可视距离和景深
  scene.fog.update(frameState);
  us.update(frameState);

  // const shadowMap = scene.shadowMap
  // if (Cesium.defined(shadowMap) && shadowMap.enabled) {
  // 	if (!Cesium.defined(scene.light) || scene.light instanceof Cesium.SunLight) {
  // 		Cesium.Cartesian3.negate(us.sunDirectionWC, scene._shadowMapCamera.direction)
  // 	} else {
  // 		Cesium.Cartesian3.clone(scene.light.direction, scene._shadowMapCamera.direction)
  // 	}
  // 	frameState.shadowMaps.push(shadowMap)
  // }

  scene._computeCommandList.length = 0;
  scene._overlayCommandList.length = 0;
  // 7. 配置视口（viewport），指定渲染的区域为整个画布的大小。
  // drawingBufferWidth 和 drawingBufferHeight 是当前画布的尺寸
  const viewport = view.viewport;
  viewport.x = 0;
  viewport.y = 0;
  viewport.width = context.drawingBufferWidth;
  viewport.height = context.drawingBufferHeight;
  // 8. 设置渲染目标为传入的FBO，这意味着接下来的渲染操作将不会直接显示在屏幕上，而是绘制到这个FBO中
  const passState = view.passState;
  // 最终绘制到传入的fbo
  passState.framebuffer = fbo;
  // 关闭了混合（blendingEnabled）和剪裁测试（scissorTest）
  passState.blendingEnabled = undefined;
  passState.scissorTest = undefined;
  passState.viewport = Cesium.BoundingRectangle.clone(
    viewport,
    passState.viewport
  );

  // if (Cesium.defined(scene.globe)) {
  // 	scene.globe.beginFrame(frameState)
  // }
  // 9. 更新环境和执行渲染命令
  scene.updateEnvironment();
  scene.updateAndExecuteCommands(passState, backgroundColor);
  // 10. 解析帧缓冲区并恢复设置
  scene.resolveFramebuffers(passState);
  // 表示渲染完成，不再使用FBO作为目标
  passState.framebuffer = undefined;

  // if (Cesium.defined(scene.globe)) {
  // 	scene.globe.endFrame(frameState)
  // 	if (!scene.globe.tilesLoaded) {
  // 		scene._renderRequested = true
  // 	}
  // }
  // 11. 结束帧并恢复设置
  context.endFrame();
  scene.globe.show = true;
  scene._defaultView.camera = preCamera;
}

/** 根据参数裁剪3DTileset, Model, 或者Entity中的model，保留法向量方向上的部分
 * @param m Cesium3DTileset, Model, Entity
 * @param centerPosition  裁剪面的中心点
 * @param normal 裁剪面的法向量
 * @returns 生成的ClippingPlaneCollection，或者entity中没有模型则返回undefined
 */
function clipAnyModel(
  m: Cesium3DTileset | Model | Entity,
  centerPosition: Cartesian3,
  normal: Cartesian3
) {
  let transform: Matrix4;
  let model: Cesium3DTileset | Model | ModelGraphics;
  // tileset有可能进行了一些变换，所以需要将transform乘上modelMatrix
  if (m instanceof Cesium3DTileset) {
    model = m;
    transform = Matrix4.multiply(
      model.modelMatrix,
      model.root.transform,
      new Matrix4()
    );
    Matrix4.inverse(transform, transform);
  } else if (m instanceof Model) {
    model = m;
    transform = Matrix4.inverse(model.modelMatrix, new Matrix4());
  } else {
    if (!m.model) return;
    transform = Transforms.eastNorthUpToFixedFrame(
      m.position!.getValue(new JulianDate())!
    );
    Matrix4.inverse(transform, transform);
    model = m.model;
  }

  const clippingPlanes = new ClippingPlaneCollection();
  const plane = Plane.fromPointNormal(centerPosition, normal);
  const clipPlane = Plane.transform(plane, transform);
  clippingPlanes.add(clipPlane);
  model.clippingPlanes = clippingPlanes;

  return clippingPlanes;
}
let camera1, camera2
/**
 * 可视化虚拟反射相机
 */
function visualizeCamera() {
  const positions = Cartesian3.fromDegreesArrayHeights(positionsArr);
  const centerPosition = calcCenterPoint(positions);
  const waterNormal =
    tCesium.cesium.viewer.scene.globe.ellipsoid.geodeticSurfaceNormal(
      centerPosition
    );
  const preCamera = Camera.clone(tCesium.cesium.viewer.camera);
  // 得到反射相机
  const reflectCamera = getMirrorCamera(preCamera, waterNormal, centerPosition);

  if (camera1 && camera2) {
    tCesium.cesium.viewer.scene.primitives.remove(camera1);
    tCesium.cesium.viewer.scene.primitives.remove(camera2);
    camera1 = null;  
    camera2 = null;
  } else {
    camera1 = new Cesium.DebugCameraPrimitive({
      camera: preCamera,
      color: Color.RED,
    })
    tCesium.cesium.viewer.scene.primitives.add(
      camera1
    );

    camera2 = new Cesium.DebugCameraPrimitive({
      camera: reflectCamera,
      color: Color.BLUE,
    })
    tCesium.cesium.viewer.scene.primitives.add(
      camera2
    );
  }
}
// 修改默认的全球水面效果
function reviseGlobeWater(viewer: any) {
  // viewer.scene.globe.showWaterEffect = false;
  const globe = viewer.scene.globe;
  console.log(globe);
  // globe._surfaceShaderSet是GlobeSurfaceShaderSet的对象
  console.log(globe._surfaceShaderSet);
  // const sp = globe._surfaceShaderSet.getShaderProgram({
  //   showOceanWaves: true
  // });
  // console.log(sp);
}
</script>

<template>
  <div id="cesiumContainer"></div>
  <button class="reflectCamera" @click="visualizeCamera">可视化反射相机</button>
</template>

<style lang="scss">
#cesiumContainer {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  position: absolute;
  top: 0;
  left: 0;
}

.reflectCamera {
  position: absolute;
  top: 50px;
  right: 50px;
  width: 110px;
  height: 30px;
}
</style>
